import DEFAULTS from './defaults';
import TEMPLATE from './template';
import render from './render';
import renderList from './render';
import resetList from './render';
import initImage from './render';
import renderImage from './render';
import resetImage from './render';


import events from './events';
import handlers from './handlers';
import methods from './methods';
import others from './others';
import {
	BUTTONS,
	CLASS_CLOSE,
	CLASS_FADE,
	CLASS_FIXED,
	CLASS_FULLSCREEN,
	CLASS_HIDE,
	CLASS_INVISIBLE,
	DATA_ACTION,
	EVENT_CLICK,
	EVENT_ERROR,
	EVENT_LOAD,
	EVENT_READY,
	NAMESPACE,
	REGEXP_SPACES,
	WINDOW,
} from './constants';
import {
	addClass,
	addListener,
	assign,
	dispatchEvent,
	forEach,
	getResponsiveClass,
	hyphenate,
	isFunction,
	isNumber,
	isPlainObject,
	isString,
	isUndefined,
	removeListener,
	setData,
	setStyle,
	toggleClass,
} from './utilities';

const AnotherViewer = WINDOW.Viewer;
const getUniqueID = ((id) => (() => {
	id += 1;
	return id;
}))(-1);

class Viewer {
	/**
	 * Create a new Viewer.
	 * @param {Element} element - The target element for viewing.
	 * @param {Object} [options={}] - The configuration options.
	 */
	constructor(element, options = {}) {
		if (!element || element.nodeType !== 1) {
			throw new Error('The first argument is required and must be an element.');
		}

		this.element = element;
		this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
		this.action = false;
		this.fading = false;
		this.fulled = false;
		this.hiding = false;
		this.imageClicked = false;
		this.imageData = {};
		this.index = this.options.initialViewIndex;
		this.isImg = false;
		this.isShown = false;
		this.length = 0;
		this.moving = false;
		this.played = false;
		this.playing = false;
		this.pointers = {};
		this.ready = false;
		this.rotating = false;
		this.scaling = false;
		this.showing = false;
		this.timeout = false;
		this.tooltipping = false;
		this.viewed = false;
		this.viewing = false;
		this.wheeling = false;
		this.zooming = false;
		this.id = getUniqueID();
		this.init();
	}

	init() {
		const {
			element,
			options
		} = this;

		if (element[NAMESPACE]) {
			return;
		}

		element[NAMESPACE] = this;

		// The `focus` option requires the `keyboard` option set to `true`.
		if (options.focus && !options.keyboard) {
			options.focus = false;
		}

		const isImg = element.localName === 'img';
		const images = [];

		forEach(isImg ? [element] : element.querySelectorAll('img'), (image) => {
			if (isFunction(options.filter)) {
				if (options.filter.call(this, image)) {
					images.push(image);
				}
			} else if (this.getImageURL(image)) {
				images.push(image);
			}
		});

		this.isImg = isImg;
		this.length = images.length;
		this.images = images;
		this.initBody();

		// Override `transition` option if it is not supported
		if (isUndefined(document.createElement(NAMESPACE).style.transition)) {
			options.transition = false;
		}

		if (options.inline) {
			let count = 0;
			const progress = () => {
				count += 1;

				if (count === this.length) {
					let timeout;

					this.initializing = false;
					this.delaying = {
						abort: () => {
							clearTimeout(timeout);
						},
					};

					// build asynchronously to keep `this.viewer` is accessible in `ready` event handler.
					timeout = setTimeout(() => {
						this.delaying = false;
						this.build();
					}, 0);
				}
			};

			this.initializing = {
				abort: () => {
					forEach(images, (image) => {
						if (!image.complete) {
							removeListener(image, EVENT_LOAD, progress);
							removeListener(image, EVENT_ERROR, progress);
						}
					});
				},
			};

			forEach(images, (image) => {
				if (image.complete) {
					progress();
				} else {
					let onLoad;
					let onError;

					addListener(image, EVENT_LOAD, onLoad = () => {
						removeListener(image, EVENT_ERROR, onError);
						progress();
					}, {
						once: true,
					});
					addListener(image, EVENT_ERROR, onError = () => {
						removeListener(image, EVENT_LOAD, onLoad);
						progress();
					}, {
						once: true,
					});
				}
			});
		} else {
			addListener(element, EVENT_CLICK, (this.onStart = ({
				target
			}) => {
				if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(
						this, target))) {
					this.view(this.images.indexOf(target));
				}
			}));
		}
	}

	build() {
		if (this.ready) {
			return;
		}

		const {
			element,
			options
		} = this;
		const parent = element.parentNode;
		const template = document.createElement('div');

		template.innerHTML = TEMPLATE;

		const viewer = template.querySelector(`.${NAMESPACE}-container`);
		const title = viewer.querySelector(`.${NAMESPACE}-title`);
		const toolbar = viewer.querySelector(`.${NAMESPACE}-toolbar`);
		const navbar = viewer.querySelector(`.${NAMESPACE}-navbar`);
		const button = viewer.querySelector(`.${NAMESPACE}-button`);
		const canvas = viewer.querySelector(`.${NAMESPACE}-canvas`);

		this.parent = parent;
		this.viewer = viewer;
		this.title = title;
		this.toolbar = toolbar;
		this.navbar = navbar;
		this.button = button;
		this.canvas = canvas;
		this.footer = viewer.querySelector(`.${NAMESPACE}-footer`);
		this.tooltipBox = viewer.querySelector(`.${NAMESPACE}-tooltip`);
		this.player = viewer.querySelector(`.${NAMESPACE}-player`);
		this.list = viewer.querySelector(`.${NAMESPACE}-list`);

		viewer.id = `${NAMESPACE}${this.id}`;
		title.id = `${NAMESPACE}Title${this.id}`;
		addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ?
			options.title[0] :
			options.title));
		addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
		toggleClass(button, CLASS_HIDE, !options.button);

		if (options.keyboard) {
			button.setAttribute('tabindex', 0);
		}

		if (options.backdrop) {
			addClass(viewer, `${NAMESPACE}-backdrop`);

			if (!options.inline && options.backdrop !== 'static') {
				setData(canvas, DATA_ACTION, 'hide');
			}
		}

		if (isString(options.className) && options.className) {
			// In case there are multiple class names
			options.className.split(REGEXP_SPACES).forEach((className) => {
				addClass(viewer, className);
			});
		}

		if (options.toolbar) {
			const list = document.createElement('ul');
			const custom = isPlainObject(options.toolbar);
			const zoomButtons = BUTTONS.slice(0, 3);
			const rotateButtons = BUTTONS.slice(7, 9);
			const scaleButtons = BUTTONS.slice(9);

			if (!custom) {
				addClass(toolbar, getResponsiveClass(options.toolbar));
			}

			forEach(custom ? options.toolbar : BUTTONS, (value, index) => {
				const deep = custom && isPlainObject(value);
				const name = custom ? hyphenate(index) : value;
				const show = deep && !isUndefined(value.show) ? value.show : value;

				if (
					!show ||
					(!options.zoomable && zoomButtons.indexOf(name) !== -1) ||
					(!options.rotatable && rotateButtons.indexOf(name) !== -1) ||
					(!options.scalable && scaleButtons.indexOf(name) !== -1)
				) {
					return;
				}

				const size = deep && !isUndefined(value.size) ? value.size : value;
				const click = deep && !isUndefined(value.click) ? value.click : value;
				const item = document.createElement('li');

				if (options.keyboard) {
					item.setAttribute('tabindex', 0);
				}

				item.setAttribute('role', 'button');
				addClass(item, `${NAMESPACE}-${name}`);

				if (!isFunction(click)) {
					setData(item, DATA_ACTION, name);
				}

				if (isNumber(show)) {
					addClass(item, getResponsiveClass(show));
				}

				if (['small', 'large'].indexOf(size) !== -1) {
					addClass(item, `${NAMESPACE}-${size}`);
				} else if (name === 'play') {
					addClass(item, `${NAMESPACE}-large`);
				}

				if (isFunction(click)) {
					addListener(item, EVENT_CLICK, click);
				}

				list.appendChild(item);
			});

			toolbar.appendChild(list);
		} else {
			addClass(toolbar, CLASS_HIDE);
		}

		if (!options.rotatable) {
			const rotates = toolbar.querySelectorAll('li[class*="rotate"]');

			addClass(rotates, CLASS_INVISIBLE);
			forEach(rotates, (rotate) => {
				toolbar.appendChild(rotate);
			});
		}

		if (options.inline) {
			addClass(button, CLASS_FULLSCREEN);
			setStyle(viewer, {
				zIndex: options.zIndexInline,
			});

			if (window.getComputedStyle(parent).position === 'static') {
				setStyle(parent, {
					position: 'relative',
				});
			}

			parent.insertBefore(viewer, element.nextSibling);
		} else {
			addClass(button, CLASS_CLOSE);
			addClass(viewer, CLASS_FIXED);
			addClass(viewer, CLASS_FADE);
			addClass(viewer, CLASS_HIDE);

			setStyle(viewer, {
				zIndex: options.zIndex,
			});

			let {
				container
			} = options;

			if (isString(container)) {
				container = element.ownerDocument.querySelector(container);
			}

			if (!container) {
				container = this.body;
			}

			container.appendChild(viewer);
		}

		if (options.inline) {
			this.render();
			this.bind();
			this.isShown = true;
		}

		this.ready = true;

		if (isFunction(options.ready)) {
			addListener(element, EVENT_READY, options.ready, {
				once: true,
			});
		}

		if (dispatchEvent(element, EVENT_READY) === false) {
			this.ready = false;
			return;
		}

		if (this.ready && options.inline) {
			this.view(this.index);
		}
	}
	renderList(){
		renderList()
	}
	resetList(){
		resetList()
	}
	initImage(){
		initImage(() => {
			renderImage();
		});
	}
	resetImage(){
		resetImage()
	}
	
	/**
	 * Get the no conflict viewer class.
	 * @returns {Viewer} The viewer class.
	 */
	static noConflict() {
		window.Viewer = AnotherViewer;
		return Viewer;
	}

	/**
	 * Change the default options.
	 * @param {Object} options - The new default options.
	 */
	static setDefaults(options) {
		assign(DEFAULTS, isPlainObject(options) && options);
	}
}

assign(Viewer.prototype, render, events, handlers, methods, others);

export default Viewer;
